using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
#if TEST
using Fadd.Commands.Tests;
using Xunit;
#endif

namespace Fadd.Commands
{
    /// <summary>
    /// Generates Object proxies.
    /// </summary>
	/// <todo>
	/// Name the proxies with generic types to allow multiple generic types ie ListInt32Proxy and ListStringProxy.
	/// Pretty up the use of properties a bit.
	/// Add tests for interface hierarchies and properties.
	/// Add support for events in interfaces.
	/// </todo>
    public class ObjectProxyGenerator
    {
		private static readonly ObjectProxyTypeNameGenerator _typeNameGenerator = new ObjectProxyTypeNameGenerator();

        private readonly ICommandDispatcher _dispatcher;

		#region The string layout for the proxy declaration : ProxyDeclaration

		private const string ProxyDeclaration =
@"{namespaces}

namespace Fadd.Commands {
public class {ProxyName} : {Type}
{
    Type _realType = typeof({Type});
    Type _myType = typeof({ProxyName});
    ICommandDispatcher _dispatcher;
    {MethodInfos}

    public {ConstructorName}(ICommandDispatcher dispatcher)
    {
        _dispatcher = dispatcher;
        {MethodInits}
    }

    
    {Methods}

	{Properties}

    public new Type GetType()
    {
        return _realType;
    }

    public Type GetMyType()
    {
        return _myType;
    }
}}";

		#endregion

		private readonly Dictionary<Type, object> _generatedProxies = new Dictionary<Type, object>();

		private readonly List<Type> _types = new List<Type>();

    	private readonly List<ObjectProxyMethod> _methods = new List<ObjectProxyMethod>();

		private readonly List<ObjectProxyProperty> _properties = new List<ObjectProxyProperty>();
		
		private string _methodInfos = string.Empty;
        private string _methodInits = string.Empty;

        /// <summary>
        /// Initializes a new instance of the <see cref="ObjectProxyGenerator"/> class.
        /// </summary>
        /// <param name="dispatcher">Dispatcher used to send events.</param>
        public ObjectProxyGenerator(ICommandDispatcher dispatcher)
        {
            Check.Require(dispatcher, "dispatcher");
            _dispatcher = dispatcher;
        }

        /// <summary>
        /// Generate an object proxy
        /// </summary>
        /// <param name="type">Type to generate a proxy for</param>
        /// <returns></returns>
        public object Generate(Type type)
        {
			if (_generatedProxies.ContainsKey(type))
				return _generatedProxies[type];

			_properties.Clear();
        	_methods.Clear();
			_methodInfos = string.Empty;
			_methodInits = string.Empty;
			_types.Clear();
            _types.Add(type);

            string code = BuildClassText(type);

            object proxy = Compile(type, code);
			_generatedProxies.Add(type, proxy);
        	return proxy;
		}

/*
		/// <summary>
		/// Retrieves a full proxy name for the type including any generic types
		/// </summary>
		/// <param name="type">The type to generate a proxy name for</param>
		private static string GetProxyName(Type type)
		{
			if(type.IsGenericType)
			{
				string name = _typeNameGenerator.GetGenericName(type);
				return name.Insert(name.IndexOf('<'), "Proxy");
			}

			return _typeNameGenerator.GetName(type);
		}
*/

		/// <summary>
		/// Compiles the proxy type declaration string that has been build
		/// </summary>
		/// <param name="type">The type from which to create a proxy</param>
		/// <param name="code">The code to compile</param>
		/// <returns>The compiled object or null if it couldn't be created</returns>
		private object Compile(Type type, string code)
        {
            Check.Require(type, "type");
            Check.NotEmpty(code, "code");

            Compiler compiler = new Compiler();
            compiler.Add(typeof(ObjectProxyGenerator));
            compiler.Add(typeof(MethodInfo));
		    Assembly generatorAssembly = compiler.Compile(code);

            string typeName = "Fadd.Commands." + _typeNameGenerator.GetName(type) + "Proxy";
            return generatorAssembly.CreateInstance(typeName, false, BindingFlags.CreateInstance,
                                                    null, new object[]{_dispatcher}, null, null);
        }

        /// <summary>
        /// Add all properties
        /// </summary>
        /// <param name="interfaceType"></param>
		protected void AddProperties(Type interfaceType)
        {
			foreach (PropertyInfo info in interfaceType.GetProperties())
			{
				ObjectProxyProperty property = new ObjectProxyProperty(info);
				_properties.Add(property);

				if (property.HasGet)
				{
					_methodInfos += "\tMethodInfo " + property.Signature + "Get;\r\n";
					_methodInits += "\t" + property.Signature + "Get = _realType.GetMethod(\"get_" + property.Name + "\");\r\n";
				}
				if (property.HasSet)
				{
					_methodInfos += "\tMethodInfo " + property.Signature + "Set;\r\n";
					_methodInits += "\t" + property.Signature + "Set = _realType.GetMethod(\"set_" + property.Name + "\");\r\n";
				}

				if (!_types.Contains(property.PropertyType))
					_types.Add(property.PropertyType);
			}
		}

        /// <summary>
        /// Build code for a proxy class.
        /// </summary>
        /// <param name="type">Type to build code for.</param>
        /// <returns>a complete class (in text).</returns>
        protected string BuildClassText(Type type)
        {
            foreach (MethodInfo info in type.GetMethods())
            {
                if (info.Name == "ToString" || info.Name == "Equals" || info.Name == "GetHashCode" || info.Name == "GetType" || info.Name.StartsWith("get_") || info.Name.StartsWith("set_"))
                    continue;

				if (!info.IsVirtual || info.IsFinal)
					continue;
            	
				ObjectProxyMethod method = new ObjectProxyMethod(info);
				_methods.Add(method);
				_methodInfos += "\tMethodInfo " + method.Signature + ";\r\n";
				_methodInits += "\t" + method.Signature + " = " + "_realType." + method.GetMethodCall + "\r\n";

            	foreach (Type neededType in method.NeededTypes)
            		if(!_types.Contains(neededType))
						_types.Add(neededType);
            }

			if(type.IsInterface)
				AddProperties(type);
        	
			foreach (Type baseInterface in type.GetInterfaces())
        		AddProperties(baseInterface);

            NameValueCollection vars = new NameValueCollection();
            vars["Type"] = _typeNameGenerator.GetGenericName(type);
			vars["ProxyName"] = _typeNameGenerator.GetName(type) + "Proxy";
			vars["ConstructorName"] = _typeNameGenerator.GetName(type) + "Proxy";
            vars["FullName"] = _typeNameGenerator.GetFullName(type);
            vars["MethodInfos"] = _methodInfos;
            vars["MethodInits"] = _methodInits;
        	
			vars["Methods"] = string.Empty;
			foreach (ObjectProxyMethod method in _methods)
				vars["Methods"] += method.Declaration + Environment.NewLine + Environment.NewLine;

			vars["Properties"] = string.Empty;
			foreach (ObjectProxyProperty property in _properties)
				vars["Properties"] += property.Declaration + Environment.NewLine + Environment.NewLine;

            string text = ProxyDeclaration;
            foreach (string key in vars)
                text = text.Replace("{" + key + "}", vars[key]);

            return text;
		}

#if TEST
		#region Tests

		[Fact]
		private static void TestGenerateClass()
		{
			CommandManager mgr = new CommandManager();
			ObjectProxyGenerator generator = new ObjectProxyGenerator(mgr);

			bool invoked = false;
			mgr.Add(typeof(ObjectProxyCommand), delegate(object source, CommandEventArgs args)
													 {
														 ObjectProxyCommand cmd = (ObjectProxyCommand)args.Command;
														 Assert.Equal(cmd.Type.Name, "TestObject");
														 invoked = true;
														 return true;
													 });

			object obj = generator.Generate(typeof(TestObject));
			TestObject gen2 = (TestObject)obj;
			gen2.DoThis();
			Assert.True(invoked);
		}


		[Fact]
		private static void TestGenerateGenericClass()
		{
			CommandManager mgr = new CommandManager();
			ObjectProxyGenerator generator = new ObjectProxyGenerator(mgr);

			bool invoked = false;
			mgr.Add(typeof(ObjectProxyCommand), delegate(object source, CommandEventArgs args)
													 {
														 ObjectProxyCommand cmd = (ObjectProxyCommand)args.Command;
														 Assert.Equal(_typeNameGenerator.GetGenericName(cmd.Type), "GenericTestObject<Char, Boolean>");
														 invoked = true;
														 return true;
													 });

			object obj = generator.Generate(typeof(GenericTestObject<char, bool>));
			GenericTestObject<char, bool> gen2 = (GenericTestObject<char, bool>)obj;
			gen2.RetrieveGenericDictionary<int, bool>(delegate { });
			Assert.True(invoked);
		}

		[Fact]
		private static void TestGenerateInterface()
		{
			CommandManager mgr = new CommandManager();
			ObjectProxyGenerator generator = new ObjectProxyGenerator(mgr);

			bool invoked = false;
			mgr.Add(typeof(ObjectProxyCommand), delegate(object source, CommandEventArgs args)
													 {
														 ObjectProxyCommand cmd = (ObjectProxyCommand)args.Command;
														 Assert.Equal(cmd.Type.Name, "ITestObject");
														 invoked = true;
														 return true;
													 });

			object obj = generator.Generate(typeof(ITestObject));
			ITestObject gen2 = (ITestObject)obj;
			gen2.DoThis();
			Assert.True(invoked);
			invoked = false;
			gen2.Id = 2;
			Assert.True(invoked);
		}

		#endregion
#endif
    }
}